package cn.grab.fispservice.upms.controller;

import cn.grab.fispapi.upms.constdict.SysUserStatus;
import cn.grab.fispapi.upms.constdict.SysUserType;
import cn.grab.fispapi.upms.dto.LoginDTO;
import cn.grab.fispapi.upms.vo.LoginVO;
import cn.grab.fispservice.config.ApplicationConfig;
import cn.grab.fispservice.system.service.SysDictItemService;
import cn.grab.fispservice.upms.model.SysMenu;
import cn.grab.fispservice.upms.model.SysUser;
import cn.grab.fispservice.upms.model.SysUserRole;
import cn.grab.fispservice.upms.service.*;
import cn.grab.fispservice.util.SMSUtils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fontana.base.annotation.MyRequestBody;
import com.fontana.base.annotation.NoAuthInterface;
import com.fontana.base.constant.CommonConstants;
import com.fontana.base.object.TokenData;
import com.fontana.base.result.Result;
import com.fontana.base.result.ResultCode;
import com.fontana.db.util.MyModelUtil;
import com.fontana.log.auditlog.AuditLog;
import com.fontana.redis.util.RedisTemplateUtil;
import com.fontana.redis.util.SessionCacheHelper;
import com.fontana.util.codec.Md5Util;
import com.fontana.util.codec.RsaUtil;
import com.fontana.util.lang.StringUtil;
import com.fontana.util.request.IpUtil;
import com.fontana.util.request.WebContextUtil;
import com.fontana.util.tools.IdUtil;
import com.fontana.util.tools.RandImageUtil;
import com.fontana.util.tools.RedisKeyUtil;
import com.fontana.util.updownload.BaseUpDownloader;
import com.fontana.util.updownload.UpDownloaderFactory;
import com.fontana.util.updownload.UploadResponseInfo;
import com.fontana.util.updownload.UploadStoreInfo;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 登录接口控制器类。
 *
 * @author cqf
 * @date 2020-08-08
 */
@RestController
@RequestMapping("/login")
@ApiSupport(order = 1)
@Api(tags = "登录接口")
@Slf4j
public class LoginController {

	@Autowired
	RedisTemplateUtil redisTemplateUtil;
	@Autowired
	private SysUserService sysUserService;
	@Autowired
	private SysPermCodeService sysPermCodeService;
	@Autowired
	private SysPermService sysPermService;
	@Autowired
	private SysMenuService sysMenuService;
	@Autowired
	private SysPermWhitelistService sysPermWhitelistService;
	@Autowired
	private SysDictItemService sysDictItemService;
	@Autowired
	private RedissonClient redissonClient;
	@Autowired
	private PasswordEncoder passwordEncoder;
	@Autowired
	private SessionCacheHelper cacheHelper;
	@Autowired
	private CaptchaService captchaService;
	@Autowired
	private ApplicationConfig appConfig;
	@Autowired
	private UpDownloaderFactory upDownloaderFactory;
	@Autowired
    private SysRoleService sysRoleService;

	/**
	 * 后台生成图形验证码
	 * @param response
	 * @param key
	 */
	/*@ApiOperation("获取验证码")
	@NoAuthInterface
	@GetMapping(value = "/randomImage/{key}")
	public Result<String> randomImage(HttpServletResponse response, @PathVariable String key){
		try {
			String code = RandomUtil.randomString(CommonConstants.BASE_CHECK_CODES,4);
			String lowerCaseCode = code.toLowerCase();
			String realKey = Md5Util.md5(lowerCaseCode + key);
			redisTemplateUtil.setExpire(realKey, lowerCaseCode, 60);
			String base64 = RandImageUtil.generate(code);
			return Result.succeed(base64);
		} catch (Exception e) {
			log.error("获取验证码出错: {}" , e.getMessage());
			return Result.failed(ResultCode.ACQUIRE_CHECK_CODES_ERROR);

		}
	}*/

	/**
	 * 发送短信
	 *
	 * @param loginVO
	 */
	@ApiOperation("发送短信接口")
	@PostMapping("/sendMsg")
	public Result<String> sendMsg(@MyRequestBody LoginVO loginVO) {
		//1.取出手机号做校验
		String phone = loginVO.getPhone();
		if (StringUtils.isNotBlank(phone)) {
			//2.生成随机验证码
			String checkCode = RandomUtil.randomNumbers(6);
			//3.调用阿里云短信服务
			SMSUtils.sendMessage("","",phone,checkCode);
			//4.存入redis并加时效
			redisTemplateUtil.setExpire("user_login_code"+phone,checkCode,300);
			//5.返回
			return Result.succeed("发送成功");
		}
		return Result.failed(ResultCode.PARAM_IS_BLANK,"手机号码不正确，请重试");
	}

	/**
	 * 手机号登陆注册
	 *
	 * @param loginVO
	 */
	@ApiOperation("手机号登录注册接口")
	@PostMapping("/toLogin")
	public Result login(@MyRequestBody LoginVO loginVO) {
		//1.取出手机号和验证码检验
		String phone = loginVO.getPhone();
		String code = loginVO.getCode();
		if (StringUtils.isBlank(phone) || StringUtils.isBlank(code)) {
			return Result.failed(ResultCode.PARAM_IS_BLANK,"手机号或验证码为空");
		}
		//2.和redis中的比较
		Object redisCode = redisTemplateUtil.get("user_login_code" + phone);
		if (null == code || !code.equals(redisCode)) {
			return Result.failed("验证码有误");
		}
		//3.判断手机号是否存在
		SysUser dbSysUser = sysUserService.getOne((Wrappers.<SysUser>lambdaQuery()
				.eq(SysUser::getPhone, phone)));
		//没有就添加
		if (null==dbSysUser) {
			dbSysUser.setPhone(phone);
			dbSysUser.setLoginName(phone);
			dbSysUser.setUserStatus(0);
			dbSysUser.setUserType(2);
			dbSysUser.setCreateTime(new Date());
			//保存至数据库
			sysUserService.save(dbSysUser);
		}
		//不是第一次，直接登录
		JSONObject jsonData = this.buildLoginData(dbSysUser);
		//删除redis的缓存验证码
		redisTemplateUtil.del("user_login_code" + phone);

		return Result.succeed(jsonData);
	}


	/**
	 * 登录接口。
	 *
	 * @param
	 * @return 应答结果对象，其中包括JWT的Token数据，以及菜单列表。
	 */
	@ApiOperation("登录接口")
	@ApiImplicitParams({
			// 这里包含密码密文，仅用于方便开发期间的接口测试，集成测试和发布阶段，需要将当前注解去掉。
			// 如果您重新生成了公钥和私钥，请替换password的缺省值。
			@ApiImplicitParam(name = "loginName", defaultValue = "admin"),
			@ApiImplicitParam(name = "password", defaultValue = "IP3ccke3GhH45iGHB5qP9p7iZw6xUyj28Ju10rnBiPKOI35sc%2BjI7%2FdsjOkHWMfUwGYGfz8ik31HC2Ruk%2Fhkd9f6RPULTHj7VpFdNdde2P9M4mQQnFBAiPM7VT9iW3RyCtPlJexQ3nAiA09OqG%2F0sIf1kcyveSrulxembARDbDo%3D")
			/*@ApiImplicitParam(name = "captchaVerification", defaultValue = "为了方便测试，这里可以修改一下代码，hardcode一个每次都ok的验证码")*/
	})
	@AuditLog(operation="用户登陆")
	@PostMapping("/doLogin")
	public Result<JSONObject> doLogin(
			@MyRequestBody LoginDTO dto
			/*@MyRequestBody String captchaVerification*/) throws Exception {
		//1.判断是否是手机号
		String loginName = dto.getLoginName();
		String phone = dto.getPhone();
		String password = dto.getPassword();
		String ph = "^[1][34578]\\d{9}$";

		SysUser user = new SysUser();
		if (phone.matches(ph)) {   //手机号密码登录
			//获取数据源
			 user = sysUserService.getOne(Wrappers.<SysUser>lambdaQuery()
					.eq(SysUser::getPhone, phone));
			//密码的解析
			password = URLDecoder.decode(password, StandardCharsets.UTF_8.name());
			password = RsaUtil.decrypt(password, CommonConstants.PRIVATE_KEY);
			//数据校验
			if (user == null || !passwordEncoder.matches(password, user.getPassword())) {
				return Result.failed(ResultCode.USER_LOGIN_ERROR);
			}
			//账号状态判断
			if (user.getUserStatus() == SysUserStatus.STATUS_LOCKED) {
				return Result.failed(ResultCode.USER_ACCOUNT_LOCKED);
			}
		} else {
			//3.校验用户
			user = sysUserService.getSysUserByLoginName(loginName);
			password = URLDecoder.decode(password, StandardCharsets.UTF_8.name());
			// NOTE: 第一次使用时，请务必阅读ApplicationConstant.PRIVATE_KEY的代码注释。
			// 执行RsaUtil工具类中的main函数，可以生成新的公钥和私钥。
			password = RsaUtil.decrypt(password, CommonConstants.PRIVATE_KEY);
			if (user == null || !passwordEncoder.matches(password, user.getPassword())) {
				return Result.failed(ResultCode.USER_LOGIN_ERROR);
			}
			if (user.getUserStatus() == SysUserStatus.STATUS_LOCKED) {
				return Result.failed(ResultCode.USER_ACCOUNT_LOCKED);
			}
		}
		// Redis中缓存的Session数据的KEY编码格式是："SESSIONID:loginName_deviceType_UUID"。
		// 其中loginName是当前用户的登录名，比如"123456@qq.com"，deviceType是一个数字枚举值，末尾是计算的UUID。
		// 因此在下面的代码中，我们先根据当前用户的loginName和设备类型，计算出Session键的前缀部分。所有包含此前缀
		// 的Session键，均可以视为同一用户相同设备登录的会话。
		String patternKey = RedisKeyUtil.getSessionIdPrefix(user.getLoginName(), WebContextUtil.getDeviceType()) + "*";
		// 通过Redisson的方法，以通配符模式的形式，直接从Redis中删除全部具有该前缀的Session数据。
		// 删除后，之前的登录会话再次访问后台接口时，由于会话信息已经从Redis中被删除，因此只能重新登录了。
		redissonClient.getKeys().deleteByPatternAsync(patternKey);
		JSONObject jsonData = this.buildLoginData(user);
		return Result.succeed(jsonData);

	}

	/**
	 * 设置密码。
	 *
	 * @return 应答结果对象。
	 */

	/*@PostMapping("/setPassword")
	public Result setPassword(@MyRequestBody String password) throws Exception {
		return sysUserService.setPassword(password);
	}

	*//**
	 * 登出操作。同时将Session相关的信息从缓存中删除。
	 *
	 * @return 应答结果对象。
	 *//*
	@AuditLog(operation="用户登出")
	@PostMapping("/doLogout")
	public Result<Void> doLogout() {
		TokenData tokenData = WebContextUtil.takeTokenFromRequest();
		if(null != tokenData) {
			cacheHelper.removeAllSessionCache(tokenData.getSessionId());
		}
		return Result.succeed();
	}*/

	/**
	 * 在登录之后，通过token再次获取登录信息。
	 * 用于在当前浏览器登录系统后，在新tab页中可以免密登录。
	 *
	 * @return 应答结果对象，其中包括JWT的Token数据，以及菜单列表。
	 */
	@GetMapping("/getLoginInfo")
	public Result<JSONObject> getLoginInfo() {
		TokenData tokenData = WebContextUtil.takeTokenFromRequest();
		// 这里解释一下为什么没有缓存menuList和permCodeList。
		// 1. 该操作和权限验证不同，属于低频操作。
		// 2. 第一次登录和再次获取登录信息之间，如果修改了用户的权限，那么本次获取的是最新权限。
		// 3. 上一个问题无法避免，因为即便缓存也是有过期时间的，过期之后还是要从数据库获取的。
		JSONObject jsonData = new JSONObject();
		jsonData.put("showName", tokenData.getShowName());
		jsonData.put("isAdmin", tokenData.getIsAdmin());
//        if (StrUtil.isNotBlank(tokenData.getHeadImageUrl())) {
//            jsonData.put("headImageUrl", tokenData.getHeadImageUrl());
//        }
		Collection<SysMenu> menuList;
		Collection<String> permCodeList;
		if (Boolean.TRUE.equals(tokenData.getIsAdmin())) {
			menuList = sysMenuService.getAllMenuList();
			permCodeList = sysPermCodeService.getAllPermCodeList();
		} else {
			menuList = sysMenuService.getMenuListByUserId(tokenData.getUserId());
			permCodeList = sysPermCodeService.getPermCodeListByUserId(tokenData.getUserId());
		}
		jsonData.put("menuList", menuList);
		jsonData.put("permCodeList", permCodeList);
		return Result.succeed(jsonData);
	}

	/**
	 * 用户修改自己的密码。
	 *
	 * @param oldPass 原有密码。
	 * @param newPass 新密码。
	 * @return 应答结果对象。
	 */
	@PostMapping("/changePassword")
	public Result<Void> changePassword(
			@MyRequestBody String oldPass, @MyRequestBody String newPass) throws Exception {
		if (StringUtil.isAnyBlank(newPass, oldPass)) {
			return Result.failed(ResultCode.PARAM_IS_BLANK);
		}
		TokenData tokenData = WebContextUtil.takeTokenFromRequest();
		SysUser user = sysUserService.getById(tokenData.getUserId());
		oldPass = URLDecoder.decode(oldPass, StandardCharsets.UTF_8.name());
		// NOTE: 第一次使用时，请务必阅读ApplicationConstant.PRIVATE_KEY的代码注释。
		// 执行RsaUtil工具类中的main函数，可以生成新的公钥和私钥。
		oldPass = RsaUtil.decrypt(oldPass, CommonConstants.PRIVATE_KEY);
		if (user == null || !passwordEncoder.matches(oldPass, user.getPassword())) {
			return Result.failed(ResultCode.USER_LOGIN_ERROR);
		}
		newPass = URLDecoder.decode(newPass, StandardCharsets.UTF_8.name());
		newPass = RsaUtil.decrypt(newPass, CommonConstants.PRIVATE_KEY);
		if (!sysUserService.changePassword(tokenData.getUserId(), newPass)) {
			return Result.failed(ResultCode.DATA_NOT_EXIST);
		}
		return Result.succeed();
	}

	/**
	 * 上传并修改用户头像。
	 *
	 * @param uploadFile 上传的头像文件。
	 */
	@PostMapping("/changeHeadImage")
	public void changeHeadImage(
			@RequestParam("uploadFile") MultipartFile uploadFile) throws Exception {
		String fieldName = "headImageUrl";
		UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(SysUser.class, fieldName);
		BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
		UploadResponseInfo responseInfo = upDownloader.doUpload(null,
				appConfig.getUploadFileBaseDir(), SysUser.class.getSimpleName(), fieldName, true, uploadFile);
		if (Boolean.TRUE.equals(responseInfo.getUploadFailed())) {
			WebContextUtil.output(HttpServletResponse.SC_FORBIDDEN,
					Result.failed(ResultCode.UPLOAD_FAILED, responseInfo.getErrorMessage()));
			return;
		}
		responseInfo.setDownloadUri("/admin/upms/login/downloadHeadImage");
		String newHeadImage = JSONArray.toJSONString(CollUtil.newArrayList(responseInfo));
		if (!sysUserService.changeHeadImage(Long.valueOf(WebContextUtil.getUserId()), newHeadImage)) {
			WebContextUtil.output(HttpServletResponse.SC_FORBIDDEN,
					Result.failed(ResultCode.DATA_NOT_EXIST));
			return;
		}
		WebContextUtil.output(Result.succeed(responseInfo));
	}

	/**
	 * 下载用户头像。
	 *
	 * @param filename 文件名。如果没有提供该参数，就从当前记录的指定字段中读取。
	 * @param response Http 应答对象。
	 */
	@GetMapping("/downloadHeadImage")
	public void downloadHeadImage(String filename, HttpServletResponse response) {
		try {
			SysUser user = sysUserService.getById(WebContextUtil.getUserId());
			if (user == null) {
				WebContextUtil.output(HttpServletResponse.SC_NOT_FOUND);
				return;
			}
			if (StrUtil.isBlank(user.getHeadImageUrl())) {
				WebContextUtil.output(HttpServletResponse.SC_BAD_REQUEST);
				return;
			}
			if (!BaseUpDownloader.containFile(user.getHeadImageUrl(), filename)) {
				WebContextUtil.output(HttpServletResponse.SC_FORBIDDEN);
				return;
			}
			String fieldName = "headImageUrl";
			UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(SysUser.class, fieldName);
			BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
			upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
					SysUser.class.getSimpleName(), fieldName, filename, true, response);
		} catch (Exception e) {
			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			log.error(e.getMessage(), e);
		}
	}

	private JSONObject buildLoginData(SysUser user) {
		int deviceType = WebContextUtil.getDeviceType();

		boolean isAdmin = user.getUserType() == SysUserType.TYPE_ADMIN;
		TokenData tokenData = new TokenData();
		String sessionId = user.getLoginName() + "_" + deviceType + "_" + IdUtil.simpleRandomUUID();
		tokenData.setUserId(user.getUserId());
		tokenData.setUserName(user.getLoginName());
		tokenData.setDeptId(user.getDeptId());

		tokenData.setIsAdmin(isAdmin);
		tokenData.setLoginName(user.getLoginName());
		tokenData.setShowName(user.getShowName());
		tokenData.setSessionId(sessionId);
		tokenData.setLoginIp(IpUtil.getRemoteIpAddress(WebContextUtil.getHttpRequest()));
		tokenData.setLoginTime(new Date());
		tokenData.setDeviceType(deviceType);
		String headImageUrl = user.getHeadImageUrl();
		if (StrUtil.isNotBlank(headImageUrl)) {
			tokenData.setHeadImageUrl(headImageUrl);
		}
		List<SysUserRole> userRoleList = sysRoleService.getSysUserRoleListByUserId(user.getUserId());
		if (CollectionUtils.isNotEmpty(userRoleList)) {
			Set<Long> userRoleIdSet = userRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toSet());
			tokenData.setRoleIds(StringUtils.join(userRoleIdSet, ","));
		}
		// 这里将TokenData存入request，登录请求时其他地方可以用
		WebContextUtil.addTokenToRequest(tokenData);
		JSONObject jsonData = new JSONObject();
		jsonData.put(TokenData.REQUEST_ATTRIBUTE_NAME, tokenData);
		jsonData.put("showName", user.getShowName());
		jsonData.put("isAdmin", isAdmin);
		Collection<SysMenu> menuList;
		Collection<String> permCodeList;
		if (isAdmin) {
			menuList = sysMenuService.getAllMenuList();
			permCodeList = sysPermCodeService.getAllPermCodeList();
		} else {
			menuList = sysMenuService.getMenuListByUserId(tokenData.getUserId());
			permCodeList = sysPermCodeService.getPermCodeListByUserId(user.getUserId());
			// 将白名单url列表合并到当前用户的权限资源列表中，便于网关一并处理。
			Collection<String> permList = sysPermService.getPermListByUserId(user.getUserId());
			permList.addAll(sysPermWhitelistService.getWhitelistPermList());
			jsonData.put("permSet", permList);
		}
		//登录时获取所有字典项(包括系统字典和常量字典)给前端保存
		jsonData.put("dictItemList", sysDictItemService.getAllDictItems());
		jsonData.put("menuList", menuList);
		jsonData.put("permCodeList", permCodeList);

		return jsonData;
	}
}